﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using System.Xml;
using System.Windows.Forms;
using MapWindow.Interfaces;
using MapWinGIS;
using System.Reflection;


namespace TerrainViewerPlugin
{
    class Main : IPlugin 
    {
        private IMapWin _mapWin;
        
        private string _toolbarName = "toolbar3D";
        private const string _btnName3D = "btn3D";
        public bool UserActivatedPlugin = false;
        private bool _show3D = false;

        private string _snapshotImageName1 = "";
        private string _snapshotImageName2 = "";
        private string _gridWindowFileName = "";
        private string _xmlFileName = "";

        private Grid _originalGrid = null;
        private Shapefile _snapshotShapefile = null;
        private Layer _snapshotLayer = null;

        private bool _3dButtonClicked = false;

        #region Private Methods

        /// <summary>
        /// Gets a grid square sub-region and saves it to a .bil file which is used by the 3D viewer
        /// </summary>
        /// <param name="sizeInGridCells">half the size of the square subsection</param>
        /// <param name="xCenter">center x point of the region shown by 3D viewer</param>
        /// <param name="yCenter">center y point of the region shown by 3D viewer</param>
        private void CreateSnapshot(MapWinGIS.Grid gr, int halfNumRows, double xCenter, double yCenter)
        {
            
            bool gridWindowCreated = false;
            bool snapshotCreated = false;
            
            //Step 1: get the center coordinates in cells
            int centerRow = 0;
            int centerCol = 0;
            int nr = gr.Header.NumberRows;
            int nc = gr.Header.NumberCols;
           
            gr.ProjToCell(xCenter, yCenter, out centerCol, out centerRow);

            //Check if we are inside grid
            if (centerRow < 0 || centerCol < 0 || centerRow > nr || centerCol > nc)
            {
                MessageBox.Show("You clicked outside of the grid boundary. please click inside the grid to display the 3D View.");
                return;
            }

            int numRows = halfNumRows * 2;
            int startRow = centerRow - halfNumRows;
            int startCol = centerCol - halfNumRows;
            int endRow = startRow + numRows - 1;
            int endCol = startCol + numRows - 1;

            float[] gridWindow = new float[numRows * numRows];
            try
            {
                bool success = gr.GetFloatWindow(startRow, endRow, startCol, endCol, ref gridWindow[0]);
                if (success == false)
                {
                    MessageBox.Show("Unable to display 3D view. There was an error reading the grid. ");
                    return;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to display 3D view " + ex.Message);
                return;
            }

            //Step 2: save grid to a .bil file (multiply all values by 100 to get short integer values)

            double cellSize = gr.Header.dX;

            //multiplication factor depends on the height span
            double minimum = Convert.ToDouble(gr.Minimum);
            double maximum = Convert.ToDouble(gr.Maximum);
            double span = maximum - minimum;
            int multFactor = 1;
            if (span < 100)
            {
                multFactor = 100;
            }

            //check for Latitude/Longitude projection
            string proj = gr.Header.Projection.ToLower();
            if (proj != "")
            {
                if (proj.IndexOf("latlong") >=0 || proj.IndexOf("longlat") >=0 )
                {
                    //100000 is an approximate latitude degree to meter conversion factor
                    cellSize = cellSize / 100000;
                }
            }
            
            short[] shortArray = new short[gridWindow.Length];
            for(int i=0; i<gridWindow.Length; i++)
            {
                if (gridWindow[i] > minimum)
                {
                    shortArray[i] = (short)((gridWindow[i] - minimum) * multFactor);
                }
                else
                {
                    shortArray[i] = (short)(0 * multFactor);
                }
            }

            int outLenBytes = 2 * shortArray.Length;
            byte[] outBytes = new byte[outLenBytes];
            Buffer.BlockCopy(shortArray, 0, outBytes, 0, outLenBytes);
            //string gridWindowFileName = Path.Combine(System.IO.Path.GetTempPath(), "snapshot.bil");
            System.IO.BinaryWriter binWriter = new System.IO.BinaryWriter(File.Open(_gridWindowFileName, FileMode.Create));
        
            try
            {
                binWriter.Write(outBytes);
                gridWindowCreated = true;
            }
            catch(Exception ex)
            {
                MessageBox.Show("There was an error loading 3D viewer data. " + ex.Message);
            }
                finally
            {
                binWriter.Flush();
                binWriter.Close();
            }

            if (gridWindowCreated == false) return;

            //Step 3: Create a snapshot image file
            
            double snapshotWidth = numRows * cellSize;
            CreateSnapshotImages(xCenter, yCenter, snapshotWidth, 512, 105);


            //Step 4: Create a maps.xml xml file
            double elevFactor = 1/ multFactor / cellSize;

            if (elevFactor <= 0 )
            {
                elevFactor = 2;
            }

            if (elevFactor > 20)
            {
                elevFactor = 2;
            }
            CreateMapXmlFile(elevFactor);
        }

        /// <summary>
        /// Creates two snapshot images that are used by the 3D viewer. First one is used as the main map image and second one is used as
        /// the overview map image. The images are saved to files: temp/snapshot1.bmp and temp/snapshot2.bmp.
        /// </summary>
        /// <param name="xCenter"></param>
        /// <param name="yCenter"></param>
        /// <param name="widthInMapUnits"></param>
        /// <param name="widthInPixels1"></param>
        /// <param name="widthInPixels2"></param>
        private void CreateSnapshotImages(double xCenter, double yCenter, double widthInMapUnits, int imageWidth1, int imageWidth2)
        {
            double halfWidth = widthInMapUnits / 2;
            double xMin = xCenter - halfWidth;
            double yMin = yCenter - halfWidth;
            double xMax = xCenter + halfWidth;
            double yMax = yCenter + halfWidth;

            //update the snapshot shapefile
            if (_snapshotShapefile == null)
            {
                MessageBox.Show("Unable to load 3D viewer. There was an error creating the snapshot of the map.");
                return;
            }

            try
            {
                MapWinGIS.Shape extentShape = new MapWinGIS.Shape();
                extentShape.Create(MapWinGIS.ShpfileType.SHP_POLYLINE);
                int numPoints = extentShape.numPoints;


                MapWinGIS.Point myPoint1 = new MapWinGIS.Point();
                myPoint1.x = xMin;
                myPoint1.y = yMin;
                extentShape.InsertPoint(myPoint1, ref numPoints);
                numPoints = extentShape.numPoints;

                MapWinGIS.Point myPoint2 = new MapWinGIS.Point();
                myPoint2.x = xMin;
                myPoint2.y = yMax;
                extentShape.InsertPoint(myPoint2, ref numPoints);
                numPoints = extentShape.numPoints;

                MapWinGIS.Point myPoint3 = new MapWinGIS.Point();
                myPoint3.x = xMax;
                myPoint3.y = yMax;
                extentShape.InsertPoint(myPoint3, ref numPoints);
                numPoints = extentShape.numPoints;

                MapWinGIS.Point myPoint4 = new MapWinGIS.Point();
                myPoint4.x = xMax;
                myPoint4.y = yMin;
                extentShape.InsertPoint(myPoint4, ref numPoints);
                numPoints = extentShape.numPoints;

                MapWinGIS.Point myPoint5 = new MapWinGIS.Point();
                myPoint5.x = xMin;
                myPoint5.y = yMin;
                extentShape.InsertPoint(myPoint5, ref numPoints);

                _snapshotShapefile.StartEditingShapes(true, null);
                _snapshotShapefile.EditClear();
                int numShapes = _snapshotShapefile.NumShapes;
                _snapshotShapefile.EditInsertShape(extentShape, ref numShapes);
                _snapshotShapefile.EditCellValue(0, _snapshotShapefile.NumShapes, "3D_extent");
                _snapshotShapefile.StopEditingShapes(true, true, null);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to load 3D viewer. There was an error creating the snapshot of the map.");
                return;
            }

            //create the actual snapshot images
            if (File.Exists(_snapshotImageName1))
            {
                File.Delete(_snapshotImageName1);
            }

            if (File.Exists(_snapshotImageName2))
            {
                File.Delete(_snapshotImageName2);
            }

            AxMapWinGIS.AxMap map1 = (AxMapWinGIS.AxMap) _mapWin.GetOCX;
            
            int snapshotHandle = _snapshotLayer.Handle;

            MapWinGIS.Image image1 = new MapWinGIS.Image();
            image1 = map1.SnapShot2(snapshotHandle, 1.0, imageWidth1) as MapWinGIS.Image;
            if (image1 != null)
            {
                image1.Save(_snapshotImageName1, false, ImageType.BITMAP_FILE, null);
            }
            image1 = null;

            MapWinGIS.Image image2 = new MapWinGIS.Image();
            image2 = map1.SnapShot2(snapshotHandle, 1.0, imageWidth2) as MapWinGIS.Image;
            if (image2 != null)
            {
                image2.Save(_snapshotImageName2, false, ImageType.BITMAP_FILE, null);
            }
            image2 = null;
        }

        /// <summary>
        /// Creates a special 'maps.xml' file used by the 3D terrain viewer program
        /// </summary>
        private void CreateMapXmlFile(double altScale)
        {
            XmlTextWriter xmlWriter = new XmlTextWriter(_xmlFileName, System.Text.Encoding.UTF8);
            xmlWriter.Formatting = Formatting.Indented;
            xmlWriter.WriteProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
            xmlWriter.WriteStartElement("maps");
            xmlWriter.Close();

            XmlDocument doc = new XmlDocument();
            doc.Load(_xmlFileName);

            XmlElement root = doc.DocumentElement;

            XmlElement mapElement = doc.CreateElement("map");
            mapElement.SetAttribute("name", "");

            XmlElement terrainElement = doc.CreateElement("terrain");
            terrainElement.SetAttribute("demfile", "snapshot.bil");
            terrainElement.SetAttribute("texturefile", "snapshot.bmp");
            terrainElement.SetAttribute("altscale", altScale.ToString());

            root.AppendChild(mapElement);
            mapElement.AppendChild(terrainElement);

            doc.Save(_xmlFileName);
            
            StringBuilder str = new StringBuilder();
            str.AppendLine("?xml version='1.0' ?>");
        }

        /// <summary>
        /// Creates a special 'snapshot' shapefile used for taking the snapshot of the map
        /// </summary>
        private Shapefile CreateSnapshotShapefile()
        {
            string sfName = Path.Combine(Path.GetTempPath(), "snapshot.shp");
            Shapefile sf = new Shapefile();
            if (!File.Exists(sfName))
            {
                
                sf.CreateNew(sfName, ShpfileType.SHP_POLYLINE);
                MapWinGIS.Field myFld = new MapWinGIS.Field();
                myFld.Name = "Name";
                myFld.Type = FieldType.STRING_FIELD;
                myFld.Width = 50;
                sf.StartEditingShapes(true, null);
                int fldIndex = 0;
                sf.EditInsertField(myFld, ref fldIndex, null);
                sf.StopEditingShapes(true, true, null);
            }
            else
            {
                sf.Open(sfName, null);
            }
            return sf;
        }

        #endregion



        #region IPlugin Members

        public string Author
        {
            get { return "Jiri Kadlec"; }
        }

        public string BuildDate
        {
            get { return "2011-06-04"; }
        }

        public string Description
        {
            get { return "Display a 3D view"; }
        }

        public void Initialize(MapWindow.Interfaces.IMapWin MapWin, int ParentHandle)
        {
            _mapWin = MapWin;
            _3dButtonClicked = false;

            Bitmap legendIcon = Images.icon3D;

            //string tempDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            
            
            //Directory for storing temporary files
            string tempDir = Path.Combine(Path.GetTempPath(), "mw3DViewer");

            if (!Directory.Exists(tempDir))
            {
                try
                {
                    Directory.CreateDirectory(tempDir);
                }
                catch
                {
                    tempDir = Path.GetTempPath();
                }
            }

            _snapshotImageName1 = tempDir + Path.DirectorySeparatorChar + "snapshot.bmp";
            _snapshotImageName2 = tempDir + Path.DirectorySeparatorChar + "background.bmp";
            _xmlFileName = tempDir + Path.DirectorySeparatorChar + "maps.xml";
            _gridWindowFileName = tempDir + Path.DirectorySeparatorChar + "snapshot.bil";

            try
            {
                //To add the toolbarbutton
                _mapWin.Toolbar.AddToolbar(_toolbarName);

                //This is used to add the His button on the MAPWindow
                MapWindow.Interfaces.ToolbarButton btn3D = _mapWin.Toolbar.AddButton(_btnName3D, _toolbarName, "", "");
                btn3D.Text = "3D View";

                btn3D.Picture = legendIcon;
                //btn3D.Text = "3D View";

                btn3D.Tooltip = "3D View - click on the map to show a 3D view of the surrounding area";
            }
            catch
            { 
            
            }
        }

        // this event fires when the "3D" button is clicked
        public void ItemClicked(string ItemName, ref bool Handled)
        {
            try
            {
                if (ItemName == _btnName3D)
                {
                    UserActivatedPlugin = true;
                    Handled = true;
                    _3dButtonClicked = true;

                    if (_show3D == false)
                    {
                        _show3D = true;

                        //create the snapshot shapefile
                        _snapshotShapefile = CreateSnapshotShapefile();

                        int existingLayerHandle = FindSnapshotLayer();
                        if (existingLayerHandle >= 0)
                        {
                            _mapWin.Layers.Remove(existingLayerHandle);
                        }
                        _snapshotLayer = _mapWin.Layers.Add(ref _snapshotShapefile);
                        //Change the mouse Cursor
                        _mapWin.View.CursorMode = tkCursorMode.cmNone;
                        _mapWin.View.MapCursor = tkCursor.crsrArrow;
                    }
                    else
                    {
                        _show3D = false;
                    }
                }
                else
                {
                    _3dButtonClicked = false;
                    UserActivatedPlugin = false;
                }
                
            }
            catch (Exception ex)
            {
                _mapWin.ShowErrorDialog(ex);
            }
        }

        private int FindSnapshotLayer()
        {
            foreach (Layer lay in _mapWin.Layers)
            {
                if (lay.LayerType == eLayerType.LineShapefile)
                {
                    if (lay.Name == "snapshot")
                    {
                        return lay.Handle;
                    }
                    
                }
            }
            return -1;
        }

        public void MapMouseDown(int Button, int Shift, int x, int y, ref bool Handled)
        {
            if (!_3dButtonClicked)
                return;
            
            foreach (Layer lay in _mapWin.Layers)
            {
                if (lay.LayerType == eLayerType.Grid)
                {
                    _originalGrid = lay.GetGridObject;
                    double xProj = 0.0;
                    double yProj = 0.0;
                    _mapWin.View.PixelToProj(x, y, ref xProj, ref yProj);
                    CreateSnapshot(_originalGrid, 256, xProj, yProj);

                    try
                    {
                        if (File.Exists(_xmlFileName) && File.Exists(_snapshotImageName1) && File.Exists(_snapshotImageName2))
                        {
                            string args = _xmlFileName;
                            string processDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
                            string terrainViewerExeFile = Path.Combine(processDir, "terrainViewer.exe");
                            System.Diagnostics.Process.Start(terrainViewerExeFile, args);
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("There was an error launching the 3D viewer. Please check that DirectX is installed. " + ex.Message);
                    }

                    break;
                }
            }
            if (_originalGrid == null)
            {
                MessageBox.Show("There are no grid layers on the map. Please add a grid layer to the map to start the 3D viewer. ");
            }
        }

        public void LayerRemoved(int Handle)
        {
            //throw new NotImplementedException();
        }

        public void LayerSelected(int Handle)
        {
           // throw new NotImplementedException();
        }

        public void LayersAdded(MapWindow.Interfaces.Layer[] Layers)
        {
            //throw new NotImplementedException();
        }

        public void LayersCleared()
        {
           // throw new NotImplementedException();
        }

        public void LegendDoubleClick(int Handle, MapWindow.Interfaces.ClickLocation Location, ref bool Handled)
        {
            //throw new NotImplementedException();
        }

        public void LegendMouseDown(int Handle, int Button, MapWindow.Interfaces.ClickLocation Location, ref bool Handled)
        {
           // throw new NotImplementedException();
        }

        public void LegendMouseUp(int Handle, int Button, MapWindow.Interfaces.ClickLocation Location, ref bool Handled)
        {
            //throw new NotImplementedException();
        }

        public void MapDragFinished(System.Drawing.Rectangle Bounds, ref bool Handled)
        {
           // throw new NotImplementedException();
        }

        public void MapExtentsChanged()
        {
            //throw new NotImplementedException();
        }

        public void MapMouseMove(int ScreenX, int ScreenY, ref bool Handled)
        {
            //throw new NotImplementedException();
        }

        public void MapMouseUp(int Button, int Shift, int x, int y, ref bool Handled)
        {
            //throw new NotImplementedException();
        }

        public void Message(string msg, ref bool Handled)
        {
           // throw new NotImplementedException();
        }

        public string Name
        {
            get { return "3D Viewer"; }
        }

        public void ProjectLoading(string ProjectFile, string SettingsString)
        {
            //throw new NotImplementedException();
        }

        public void ProjectSaving(string ProjectFile, ref string SettingsString)
        {
            int existingLayerHandle = FindSnapshotLayer();
            if (existingLayerHandle >= 0)
            {
                _mapWin.Layers.Remove(existingLayerHandle);
            }
        }

        public string SerialNumber
        {
            get { return ""; }
        }

        public void ShapesSelected(int Handle, MapWindow.Interfaces.SelectInfo SelectInfo)
        {
            //throw new NotImplementedException();
        }

        public void Terminate()
        {
           // throw new NotImplementedException();
            //Remove the toolbar and button
            _3dButtonClicked = false;
            try
            {
                Toolbar t = _mapWin.Toolbar;
                t.RemoveButton(_btnName3D);
                t.RemoveToolbar(_toolbarName);
            }
            catch { }          
        }

        public string Version
        {
            get { return "0.2"; }
        }

        #endregion
    }
}
